#ifndef BEZIERCUBSURF_H_
#include "BezierCubSurf.h"
#endif

#include<iostream>

using namespace std;
using std::vector;

GLfloat * BezierCubSurf::static_vertex_buffer = NULL;
GLuint * BezierCubSurf::static_index_buffer = NULL;
GLfloat * BezierCubSurf::static_normal_buffer = NULL;
GLfloat * BezierCubSurf::static_tangent_buffer = NULL;
GLfloat * BezierCubSurf::static_texture_buffer = NULL;

GLuint BezierCubSurf::static_vertex_buffer_size = 0;
GLuint BezierCubSurf::static_index_buffer_size = 0;
GLuint BezierCubSurf::static_normal_buffer_size = 0;

BezierCubSurf :: BezierCubSurf(GLuint program, GLfloat points[TAMPATCHBEZIER][TAMPATCHBEZIER][3]) : Figure(program){
 
	copy(&points[0][0][0], &points[0][0][0] + TAMPATCHBEZIER*TAMPATCHBEZIER*3, &controlPoints[0][0][0]);
	calculateSurface();
	calculateNormals();
	crearDatos();
	/*instantiate_buffer();

	this->vertex_buffer = static_vertex_buffer;
	this->index_buffer = static_index_buffer;
	this->normal_buffer = static_normal_buffer;

	this->vertex_buffer_size = static_vertex_buffer_size;
	this->index_buffer_size = static_index_buffer_size;
	this->normal_buffer_size = static_normal_buffer_size;
	this->texture_buffer = static_texture_buffer;
	//this->tangent_buffer = static_tangent_buffer;
	 *
	 */
}

void BezierCubSurf::crearDatos(void) {
	DatosVertice datos;
	size = TAMBEZIERSURF*TAMBEZIERSURF*2;
	vertices = new DatosVertice[size];
	index_size = size;
	indices = new unsigned int[index_size];
	//Asigno vertices y normales. Aquí no se utilizaran las tangentes
	int pos=0;
	for (size_t y=0 ; y<TAMBEZIERSURF ; y++){
		for (size_t x=0 ; x<TAMBEZIERSURF ; x++){
			glm::vec4 Position = glm::vec4(surface[x][y][0],surface[x][y][1],surface[x][y][2],1.0);
			//set_coords3(static_vertex_buffer, pos++, Position[0], Position[1], Position[2]);
			datos.position = glm::vec3(Position[0],Position[1],Position[2]);
			glm::vec4 Position2 = glm::vec4(normals[x][y][0],normals[x][y][1],normals[x][y][2],1.0);
			datos.normal = glm::vec3(Position2[0],Position2[1],Position2[2]);
			datos.color = glm::vec3(0.5,0.5,0.5);
			vertices[pos++] = datos;

			Position = glm::vec4(surface[x][y+1][0],surface[x][y+1][1],surface[x][y+1][2],1.0);
			Position2 = glm::vec4(normals[x][y][0],normals[x][y][1],normals[x][y][2],1.0);
			if(not ( ( x== (TAMBEZIERSURF-1) ) && ( y == (TAMBEZIERSURF-1) ) ) ) {
				datos.position = glm::vec3(Position[0],Position[1],Position[2]);
				datos.normal = glm::vec3(Position2[0],Position2[1],Position2[2]);
				datos.color = glm::vec3(0.5,0.5,0.5);
				vertices[pos++] = datos;
				//set_coords3(static_vertex_buffer, pos++, Position2[0], Position2[1], Position2[2]);
			}
		}
	}
	for (uint i = 0; i < index_size; i++) {
		indices[i] = i;
	}

	for (uint i = 0; i < size; i+=4) {
		if (i+4>size) break;
		vertices[i].texture = glm::vec2(0.0,0.0);
		vertices[i+1].texture = glm::vec2(1.0,0.0);
		vertices[i+2].texture = glm::vec2(1.0,1.0);
		vertices[i+3].texture = glm::vec2(0.0,1.0);
	}


	glGenBuffers(1, &vertex_buffer_object);
	glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_object);
	glGenBuffers(1, &index_buffer_object);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer_object);

	glBufferData(GL_ARRAY_BUFFER, size * sizeof(DatosVertice), vertices, GL_STATIC_DRAW);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, index_size * sizeof(unsigned int), indices, GL_STATIC_DRAW);

	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	//this->type = GL_TRIANGLES;
	this->type = GL_TRIANGLE_STRIP;
}

BezierCubSurf::~BezierCubSurf(void) {
	delete this->vertex_buffer;
}

void BezierCubSurf::create_vertex_buffer(void) {
	//Especifico las coordenadas a partir del parche de bezier.
	int pos=0;
	for (size_t y=0 ; y<TAMBEZIERSURF ; y++){
		for (size_t x=0 ; x<TAMBEZIERSURF ; x++){
			glm::vec4 Position = glm::vec4(surface[x][y][0],surface[x][y][1],surface[x][y][2],1.0);
			set_coords3(static_vertex_buffer, pos++, Position[0], Position[1], Position[2]);

			glm::vec4 Position2 = glm::vec4(surface[x][y+1][0],surface[x][y+1][1],surface[x][y+1][2],1.0);
			if(not ( ( x== (TAMBEZIERSURF-1) ) && ( y == (TAMBEZIERSURF-1) ) ) ) {
				set_coords3(static_vertex_buffer, pos++, Position2[0], Position2[1], Position2[2]);
			}

		}
	}
}

void BezierCubSurf::create_texture_buffer(void) {
	//Asigno la textura, usa la posibilidad que que la textura se mapee más alla de (0,1), es decir que se repita en los bordes.
	int pos=0;
	while ( pos < (vertex_buffer_size/3) ) { //Revisar aqui si es < o <=
		set_coords2(static_texture_buffer, pos++, 0,0);
		if (!(pos < (vertex_buffer_size/3))) return;
		set_coords2(static_texture_buffer, pos++, 0,1);
		if (!(pos < (vertex_buffer_size/3))) return;
		set_coords2(static_texture_buffer, pos++, 1,0);
		if (!(pos < (vertex_buffer_size/3))) return;
		set_coords2(static_texture_buffer, pos++, 1,1);
	}
}

void BezierCubSurf::create_normal_buffer(void) {
	//Crea la tira de vertices normales a partir de lo generado.
	int pos=0;
	for (size_t y=0 ; y<TAMBEZIERSURF; y++){
	 	for (size_t x=0 ; x<TAMBEZIERSURF ; x++){
			glm::vec4 Position2 = glm::vec4(normals[x][y][0],normals[x][y][1],normals[x][y][2],1.0);
	 		set_coords3(static_normal_buffer, pos++, Position2[0],Position2[1],Position2[2]);
	 		glm::vec4 Position = glm::vec4(normals[x][y+1][0],normals[x][y+1][1],normals[x][y+1][2],1.0);
	 		set_coords3(static_normal_buffer, pos++, Position[0],Position[1],Position[2]);
	 		//fprintf(stderr, "%i \n", pos);
		}
	}
}

void BezierCubSurf::create_index_buffer(void) {
	for (uint i = 0; i < static_vertex_buffer_size; i++) {
		static_index_buffer[i] = i;
	}
}

void BezierCubSurf::instantiate_buffer() {
	static_vertex_buffer_size = TAMBEZIERSURF*TAMBEZIERSURF*2;
	static_normal_buffer_size = TAMBEZIERSURF*TAMBEZIERSURF*2;
	static_index_buffer_size = TAMBEZIERSURF*TAMBEZIERSURF*2;
	//static_normal_buffer_size = 3 * 8 * 3;

	static_vertex_buffer = new GLfloat[3 * static_vertex_buffer_size];
	static_normal_buffer = new GLfloat[3 * static_normal_buffer_size];
	//static_tangent_buffer = new GLfloat[3 * static_normal_buffer_size];
	static_index_buffer = new GLuint[static_index_buffer_size];
	static_texture_buffer = new GLfloat[2 * static_normal_buffer_size];

	create_vertex_buffer();
	create_index_buffer();
	create_normal_buffer();
	create_texture_buffer();
}

void BezierCubSurf :: calculateSurface(){

 size_t posAcumX = 0;
 size_t posAcumY = 0;

 for (float v=0; v<=1;v+=(float)(1/(float)(TAMBEZIERSURF-1)) ){
	posAcumX = 0;
 	for (float u=0; u<=1;u+=(float)(1/(float)(TAMBEZIERSURF-1)) ){
		float x = 0;
		float y = 0;
		float z = 0;

		for (size_t i=0; i<=3;i++){
			for (size_t j=0; j<=3;j++){
				float baseIJ = baseBezier(3,i,u)*baseBezier(3,j,v);

				x += baseIJ*controlPoints[i][j][0];
				y += baseIJ*controlPoints[i][j][1];
				z += baseIJ*controlPoints[i][j][2];
			}		
		}
		surface[posAcumX][posAcumY][0] = x;
		surface[posAcumX][posAcumY][1] = y;
		surface[posAcumX][posAcumY][2] = z;

		posAcumX++;
 	}
	posAcumY++;
 }
}

//  x0 x1 x2 
//  y0 y1 y2
//
//
// ( x1*y2-x2*y1 ; -(x0*y2-x2*y0) ; x0*y1-x1*y0)



void BezierCubSurf :: calculateNormals(){
 float h = 0.01;
 for (size_t y=0 ; y<TAMBEZIERSURF ; y++){
 	for (size_t x=0 ; x<TAMBEZIERSURF ; x++){	

		if((x>0)&&(x<TAMBEZIERSURF-1)&&(y>0)&&(y<TAMBEZIERSURF-1))
			calculateNormalForMiddlePoint(x,y,h);

		else if((x!=TAMBEZIERSURF-1)&&(y!=TAMBEZIERSURF-1))
			calculateNormalForPositBorderPoint(x,y,h);

		else if((x!=0)&&(y!=0))
			calculateNormalForNegBorderPoint(x,y,h);	
 	}
 }
}

// fi' = (fi+1 - fi-1)/(2h) centrada
void BezierCubSurf :: calculateNormalForMiddlePoint(size_t x,size_t y,float h){
	float ant[3] = {surface[x-1][y][0],surface[x-1][y][1],surface[x-1][y][2]};
	float sig[3] = {surface[x+1][y][0],surface[x+1][y][1],surface[x+1][y][2]};

	float dx[3] = {(sig[0]-ant[0])/(2*h),(sig[1]-ant[1])/(2*h),(sig[2]-ant[2])/(2*h)};

	float arr[3] = {surface[x][y+1][0],surface[x][y+1][1],surface[x][y+1][2]};
	float abaj[3] = {surface[x][y-1][0],surface[x][y-1][1],surface[x][y-1][2]};

	float dy[3] = {(arr[0]-abaj[0])/(2*h),(arr[1]-abaj[1])/(2*h),(arr[2]-abaj[2])/(2*h)};

	float normal[3] = {(dx[1]*dy[2]-dx[2]*dy[1]),(dx[2]*dy[0]-dx[0]*dy[2]),(dx[0]*dy[1]-dx[1]*dy[0]) };
	float norm      = sqrt(normal[0]*normal[0] + normal[1]*normal[1] + normal[2]*normal[2]);		
			
	normals[x][y][0] = normal[0]/norm;
	normals[x][y][1] = normal[1]/norm;
	normals[x][y][2] = normal[2]/norm;		

	//cout << "\n\nn: {" << normals[x][y][0]  << ";" << normals[x][y][1] << ";" << normals[x][y][2] << "}";
}

// fi' = (fi+1 - fi)/(h) en adelanto
void  BezierCubSurf :: calculateNormalForPositBorderPoint(size_t x,size_t y,float h){

	float act[3] = {surface[x][y][0],surface[x][y][1],surface[x][y][2]};
	float sig[3] = {surface[x+1][y][0],surface[x+1][y][1],surface[x+1][y][2]};
	float arr[3] = {surface[x][y+1][0],surface[x][y+1][1],surface[x][y+1][2]};

	float dx[3] = {(sig[0]-act[0])/(h),(sig[1]-act[1])/(h),(sig[2]-act[2])/(h)};
	float dy[3] = {(arr[0]-act[0])/(h),(arr[1]-act[1])/(h),(arr[2]-act[2])/(h)};

	float normal[3] = {(dx[1]*dy[2]-dx[2]*dy[1]),(dx[2]*dy[0]-dx[0]*dy[2]),(dx[0]*dy[1]-dx[1]*dy[0]) };
	float norm      = sqrt(normal[0]*normal[0] + normal[1]*normal[1] + normal[2]*normal[2]);		
			
	normals[x][y][0] = normal[0]/norm;
	normals[x][y][1] = normal[1]/norm;
	normals[x][y][2] = normal[2]/norm;		
}

// fi' = (fi - fi-1)/(h) en atraso
void  BezierCubSurf :: calculateNormalForNegBorderPoint(size_t x,size_t y,float h){

	float act[3] = {surface[x][y][0],surface[x][y][1],surface[x][y][2]};
	float ant[3] = {surface[x-1][y][0],surface[x-1][y][1],surface[x-1][y][2]};
	float abaj[3] = {surface[x][y-1][0],surface[x][y-1][1],surface[x][y-1][2]};

	float dx[3] = {(act[0]-ant[0])/(h),(act[1]-ant[1])/(h),(act[2]-ant[2])/(h)};
	float dy[3] = {(act[0]-abaj[0])/(h),(act[1]-abaj[1])/(h),(act[2]-abaj[2])/(h)};

	float normal[3] = {(dx[1]*dy[2]-dx[2]*dy[1]),(dx[2]*dy[0]-dx[0]*dy[2]),(dx[0]*dy[1]-dx[1]*dy[0]) };
	float norm      = sqrt(normal[0]*normal[0] + normal[1]*normal[1] + normal[2]*normal[2]);		
			
	normals[x][y][0] = normal[0]/norm;
	normals[x][y][1] = normal[1]/norm;
	normals[x][y][2] = normal[2]/norm;		
}

void BezierCubSurf :: drawNormals(glm::mat4 mat){
 
 for (size_t y=0 ; y<TAMBEZIERSURF ; y+=DETAILBEZIERSURF){
 	for (size_t x=0 ; x<TAMBEZIERSURF ; x+=DETAILBEZIERSURF){	
		glBegin(GL_LINES);

		glm::vec4 Position = glm::vec4(surface[x][y][0],surface[x][y][1],surface[x][y][2],1.0) * mat;
 		glVertex3f(Position[0],Position[1],Position[2]);

		glm::vec4 Position2 = glm::vec4(surface[x][y][0]+normals[x][y][0],surface[x][y][1]+normals[x][y][1],surface[x][y][2]+normals[x][y][2],1.0) * mat;
 		glVertex3f(Position2[0],Position2[1],Position2[2]);
	
		 glEnd();
	}
  }
}

void BezierCubSurf::draw(GLuint program, glm::mat4 & world_matrix) {
	GLuint location;
	glm::mat4 matriz = world_matrix * this->model_matrix;
	calculate_and_bind_normal_matrix(program, matriz);
	bind_model_matrix(program,matriz);

	/*glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_NORMAL_ARRAY);
	//glEnableClientState(GL_TEXTURE_COORD_ARRAY);

	//bind_tangent_buffer("tangent");

	glVertexPointer(3, GL_FLOAT, 0, vertex_buffer);
	glNormalPointer(GL_FLOAT, 0, normal_buffer);
	//glTexCoordPointer(2, GL_FLOAT, 0, texture_buffer);

	glDrawElements(GL_TRIANGLE_STRIP, index_buffer_size, GL_UNSIGNED_INT, index_buffer);

	if (this->show_normals) {
		draw_normals();
	}

	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_NORMAL_ARRAY);
	//glDisableClientState(GL_TEXTURE_COORD_ARRAY);*/
	int vertex = glGetAttribLocation(program, "vertex"); //0
	int normal = glGetAttribLocation(program, "normal"); //1
	int tangent = glGetAttribLocation(program, "tangent"); //2
	int color = glGetAttribLocation(program, "color"); //3
	int UV = glGetAttribLocation(program, "UV"); //4

	//Para suavizar!


	glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_object);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer_object);

	glEnableVertexAttribArray(vertex);
	glVertexAttribPointer(vertex, 3, GL_FLOAT, GL_FALSE, sizeof(DatosVertice), 0);

	glEnableVertexAttribArray(normal);
	glVertexAttribPointer(normal, 3, GL_FLOAT, GL_FALSE, sizeof(DatosVertice), (void*) (3 * sizeof(float)));

	glEnableVertexAttribArray(tangent);
	glVertexAttribPointer(tangent, 3, GL_FLOAT, GL_FALSE, sizeof(DatosVertice), (void*) (6 * sizeof(float)));

	glEnableVertexAttribArray(color);
	glVertexAttribPointer(color, 3, GL_FLOAT, GL_FALSE, sizeof(DatosVertice), (void*) (9 * sizeof(float)));

	glEnableVertexAttribArray(UV);
	glVertexAttribPointer(UV, 2, GL_FLOAT, GL_FALSE, sizeof(DatosVertice), (void*) (12 * sizeof(float)));

	// TEXTURE2D
	if (with_texture) {

		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, this->texture);
		location = glGetUniformLocation(program, "Texture");
		if (location >= 0) {
			glUniform1i(location, 0);
		}
		location = glGetUniformLocation(program, "with_texture");
		if (location >= 0) {
			glUniform1i(location, 1);
		}
	} else {
		location = glGetUniformLocation(program, "with_texture");
		if (location >= 0) {
			glUniform1i(location, 0);
		}
	}

	//Suavizado
	/*glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_POLYGON_SMOOTH);
	glEnable(GL_LINE_SMOOTH);*/

	glDrawElements(this->type, index_size, GL_UNSIGNED_INT, 0);
/*	glDisable(GL_BLEND);
	glDisable(GL_POLYGON_SMOOTH);
	glDisable(GL_LINE_SMOOTH);*/
	glDisableVertexAttribArray(vertex);
	glDisableVertexAttribArray(normal);
	glDisableVertexAttribArray(tangent);
	glDisableVertexAttribArray(color);
	glDisableVertexAttribArray(UV);

	if (with_texture) {
		glBindTexture(GL_TEXTURE_2D, 0);
	}

	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}

void BezierCubSurf :: drawSurface(glm::mat4 world_matrix){
	glm::mat4 mat = (world_matrix * this->model_matrix);
	for (size_t y=0 ; y<TAMBEZIERSURF-1 ; y++){
		glPointSize(1.0);
		glBegin(GL_TRIANGLE_STRIP);

		for (size_t x=0 ; x<TAMBEZIERSURF ; x++){

			glm::vec4 Position = glm::vec4(surface[x][y][0],surface[x][y][1],surface[x][y][2],1.0) * mat;
			glVertex3f(Position[0],Position[1],Position[2]);

			glm::vec4 Position2 = glm::vec4(surface[x][y+1][0],surface[x][y+1][1],surface[x][y+1][2],1.0) * mat;
			glVertex3f(Position2[0],Position2[1],Position2[2]);
		}
		glEnd();
	}
}

void BezierCubSurf :: drawControlPoints(){
 glPointSize(1.0);
 glBegin(GL_POINTS);
 for (size_t i=0;i<TAMPATCHBEZIER;i++){
	for (size_t j=0;j<TAMPATCHBEZIER;j++){
		glVertex3f(controlPoints[i][j][0],controlPoints[i][j][1],controlPoints[i][j][2]);
	}
  }
 glEnd();
}

float BezierCubSurf :: baseBezier(size_t n, size_t j,float t){
 return (Combinatoria(n,j)*pow(1-t,n-j)*pow(t,j));
}

long BezierCubSurf :: Combinatoria(long a, long b){
 if( a <= 1 )
	return 1;
 return (factorial(a) / (factorial(b) * factorial(a - b)));
}

long BezierCubSurf :: factorial(long n){
 long tot = 1;

 for(long i = 2; i <= n; i++) 
	tot *= i;

 return tot;
}

float BezierCubSurf :: pow(float base,size_t exp){

 if(exp == 0) return 1;

 float result = base;
 for(size_t i = 2; i<=exp ; i++)
 	result*=base;
 return result;
}
